package com.lulu.gateway;

import com.lulu.common.model.entity.InterfaceInfo;
import com.lulu.common.model.entity.User;
import com.lulu.common.model.entity.UserInterfaceInfo;
import com.lulu.common.service.InnerInterfaceInfoService;
import com.lulu.common.service.InnerUserInterfaceInfoService;
import com.lulu.common.service.InnerUserService;
import com.lulu.sdk.utils.SignUtils;
import lombok.extern.slf4j.Slf4j;
import org.apache.dubbo.config.annotation.DubboReference;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.core.Ordered;
import org.springframework.core.io.buffer.DataBuffer;
import org.springframework.core.io.buffer.DataBufferUtils;
import org.springframework.data.annotation.Reference;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.http.server.reactive.ServerHttpResponse;
import org.springframework.stereotype.Component;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;

import javax.annotation.Resource;
import java.net.InetSocketAddress;
import java.net.URI;
import java.util.*;
import java.util.concurrent.TimeUnit;

/**
 * @author wlc
 * @version 1.0
 */
@Slf4j
@Component
public class CustomGlobalFilter implements GlobalFilter, Ordered {

    @DubboReference
    private InnerUserService innerUserService;

    @DubboReference
    private InnerUserInterfaceInfoService innerUserInterfaceInfoService;

    @DubboReference
    private InnerInterfaceInfoService innerInterfaceInfoService;

    private final ArrayList<String> WHITE_LIST = new ArrayList<>(Collections.singletonList("127.0.0.1"));

    @Resource
    private StringRedisTemplate stringRedisTemplate;

    private User user;
    private final Object lock = new Object();


    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        ServerHttpRequest request = exchange.getRequest();
        log.info("请求唯一标识: " + request.getId());
        String path = request.getPath().toString();
        log.info("请求路径: " + request.getPath());
        String uri = request.getURI().toString();
        log.info("请求uri: " + uri);
        String method = request.getMethod().toString();
        log.info("请求方法: " + method);
        log.info("请求参数: " + request.getQueryParams());
        String localHostString = request.getLocalAddress().getHostString();
        log.info("请求来源地址: " + localHostString);
        String remoteHostAddress = request.getRemoteAddress().getHostString();
        log.info("请求地址: " + remoteHostAddress);
        ServerHttpResponse response = exchange.getResponse();

        // todo 可配置的白名单
        if (!WHITE_LIST.contains(remoteHostAddress)) {
            response.setStatusCode(HttpStatus.FORBIDDEN);
            return response.setComplete();
        }

        // 签名认证
        boolean access = access(request);
        if (!access) {
            response.setStatusCode(HttpStatus.FORBIDDEN);
            return response.setComplete();
        }

        // 校验请求接口是否存在
        InterfaceInfo interfaceInfo = null;
        try {
            interfaceInfo = innerInterfaceInfoService.getInterfaceInfo(path, method);
        } catch (Exception e) {
            log.error("getInterfaceInfo error", e);
        }

        if (interfaceInfo == null||interfaceInfo.getStatus()==0) {
            response.setStatusCode(HttpStatus.NOT_ACCEPTABLE);
            return response.setComplete();
        }

        // 判断剩余次数
        UserInterfaceInfo userInterfaceInfo = innerUserInterfaceInfoService.getUserInterfaceInfo(user.getId(), interfaceInfo.getId());
        if (userInterfaceInfo.getLeftNum() < 1) {
            response.setStatusCode(HttpStatus.NOT_ACCEPTABLE);
            return response.setComplete();
        }

        // 后置过滤器处理接口调用数量增减业务
        Long id = interfaceInfo.getId();
        return chain.filter(exchange).then(Mono.fromRunnable(() -> {
            HttpStatus statusCode = exchange.getResponse().getStatusCode();
            log.info("接口调用响应码: " + statusCode);
            if (HttpStatus.OK.equals(statusCode)) {
                try {
                    // 调用次数处理,加锁防止超卖问题
                    synchronized (lock){
                        boolean b = innerUserInterfaceInfoService.invokeCount(user.getId(), id);
                    }
                } catch (Exception e) {
                    log.error("invokeCount error", e);
                }
            }
        }));
    }

    /**
     * 签名认证
     *
     * @param request
     * @return
     */
    private boolean access(ServerHttpRequest request) {
        HttpHeaders headers = request.getHeaders();
        String accessKey = headers.getFirst("accessKey");
        String timestamp = headers.getFirst("timestamp");
        String nonce = headers.getFirst("nonce");
        String sign = headers.getFirst("sign");

        // 数据库中查询是否存在该签名
        try {
            user = innerUserService.getInvokeUser(accessKey);
        } catch (Exception e) {
            log.error("getInvokeUser error", e);
        }
        // 用户不存在
        if (user == null) {
            return false;
        }

        // ----开始防重放处理----
        // 将随机数保存到redis上，如果请求的随机数和redis中的一样则拒绝请求，不一样就在redis中重新设置传过来的随机数
        String redisKey = "api:accessKey:" + accessKey;
        if (Integer.parseInt(nonce) >= 10000 || nonce.equals(stringRedisTemplate.opsForValue().get(redisKey))) {
            return false;
        }
        stringRedisTemplate.opsForValue().set(redisKey, accessKey, 30, TimeUnit.SECONDS);

        // 限定时间为30s
        long limitTime = System.currentTimeMillis() / 1000 - 30;
        if (Long.parseLong(timestamp) < limitTime) {
            return false;
        }

        // 校验签名
        Map<String, String> map = new HashMap<>();
        map.put("accessKey", accessKey);
        map.put("timestamp", timestamp);
        map.put("nonce", nonce);

        String serverSign = SignUtils.genSign(map, user.getAccessSecret());

        return serverSign != null && serverSign.equals(sign);
    }

    @Override
    public int getOrder() {
        return 0;
    }
}
